Adapter pattern

适配器模式

适配器模式是个相当好理解的模式,可以参考生活中的电源适配器。

Design_pattern_adapter_1

假设已有一个软件系统,我们希望它能和一个新的厂商类库搭配使用,但是这个新厂商所设计的接口和旧厂商的接口不同。

Design_pattern_adapter_2

这时我们就可以写一个类,实现新厂商接口,但真正提供服务是被封装的旧厂商(反过来也可以,甚至是双向适配器)。外部客户只需要调用新厂商接口,而不需要直到内部是谁实现的。这个适配器相当于一个中间人,将客户锁发出的请求转成厂商类所能理解的请求。

Design_pattern_adapter_3

Design_pattern_adapter_4

假设我们缺少鸭子对象,需要使用火鸡对象来冒充鸭子。我们可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface Duck {
public void quack();
public void fly();
}

public class MallardDuck implements Duck {
public void quack() {
System.out.println("Quack");
}

public void fly() {
System.out.println("I'm flying");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface Turkey {
public void gobble();
public void fly();
}

public class WildTurkey implements Turkey {
public void gobble() {
System.out.println("Gobble gobble");
}

public void fly() {
System.out.println("I'm flying a short distance");
}
}

写个适配器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TurkeyAdapter implements Duck {
Turkey turkey;

public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}

public void quack() {
turkey.gobble();
}

public void fly() {
for(int i=0; i < 5; i++) {
turkey.fly();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DuckTestDrive {
public static void main(String[] args) {
MallardDuck duck = new MallardDuck();

WildTurkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);

System.out.println("The Turkey says...");
turkey.gobble();
turkey.fly();

System.out.println("\nThe Duck says...");
testDuck(duck);

System.out.println("\nThe TurkeyAdapter says...");
testDuck(turkeyAdapter);
}

static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}

这里鸭子是目标接口,火鸡就是被适配者。

客户使用适配器的过程如下:

  • 客户通过目标接口调用适配器的方法对适配器发出请求。
  • 适配器使用陪被适配者接口执行请求。
  • 客户收到调用的结果,但并未察觉这一切是适配器在起转换作用。

同样的,我们也可以写一个适配器将鸭子转为火鸡。

定义适配器模式

适配器模式将一个类的接口,转换为客户期望的另一个接口。适配器让原本接口不兼容的类可以互相合作。

同样适配器可以将改变的部分封装起来,被适配者接口改变时,客户端代码也不需要修改。

Design_pattern_adapter_5

对象适配器和类适配器

适配器主要有两类:对象适配器和类适配器。前面在鸭子的例子使用的是对象适配器。对象适配器使用的是组合,而类适配器使用的是继承(多继承)。

Design_pattern_adapter_6

Java的枚举器

Design_pattern_adapter_7

Design_pattern_adapter_8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class EnumerationIterator implements Iterator {
Enumeration enumeration;

public EnumerationIterator(Enumeration enumeration) {
this.enumeration = enumeration;
}

public boolean hasNext() {
return enumeration.hasMoreElements();
}

public Object next() {
return enumeration.nextElement();
}

public void remove() {
throw new UnsupportedOperationException();
}
}
1
2
3
4
5
6
7
8
9
public class EnumerationIteratorTestDrive {
public static void main (String args[]) {
Vector v = new Vector(Arrays.asList(args));
Iterator iterator = new EnumerationIterator(v.elements());
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

比较装饰器、适配器和外观:

装饰者:不改变接口,但加入责任
适配器:将一个接口转成另一个接口
外观:让接口更简单

外观模式

外观模式的意图是简化接口,而适配器的意图是将接口转换。使用外观模式,我们通过创建一个接口简化而统一的类,用来包装子系统中一个或多个复杂的类。

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

Design_pattern_adapter_10

这也符合了OO的另一个设计原则:最少知识原则
最少知识原则:只和你的密友谈话。

这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中的一部分,会信息到更多的部分。我们需要尽量使类对其他类对象的依赖尽可能的少。除了上面的外观模式,另一个指导原则就是:就任何对象而言,在该对象的方法内,我们只应该调用属于一下范围的方法:

  • 该对象本身
  • 被当做方法的参数而传递进来的对象
  • 此方法所创建或实例化的任何对象
  • 对象的任何组件(组合方式的成员变量)

Design_pattern_adapter_11

Design_pattern_adapter_12